#include <boost/numeric/ublas/io.hpp>

#include "util.h"
#include "gl_util.h"
#include "shader.h"

#include "inverseJulia2.h"
#include "julia4.h"
#include "julia2.h"

using namespace std;
using namespace fractals;

const bool START_FULLSCREEN=false;

Fractal *fractal;
string fractalType;

void check(GLenum err) {
	if(err != GLEW_OK)
		die(glewGetErrorString(err));
}

void versionCheck() {
    if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
        return;

    die("No shader support!");
}

void toggleFullscreen() {
	static bool isFullscreen=START_FULLSCREEN;

	if(isFullscreen) {
		glutPositionWindow(100, 100);
		glutReshapeWindow(300,300);
	} else {
		glutFullScreen();
	}

	isFullscreen=!isFullscreen;
}

int viewportWidth() { 
	GLint dims[4]; 
	glGetIntegerv(GL_VIEWPORT, dims);
	return dims[2];
}

int viewportHeight() {
	GLint dims[4]; 
	glGetIntegerv(GL_VIEWPORT, dims);
	return dims[3];
}

string getStatusLine() {
	string s(fractalType);
	
	s.append(" - ");
	s.append(getFps());
	s.append("fps");

	return s;
}

void draw(){
	bool didClear;
	fractal->draw(didClear);

	string statusLine=getStatusLine();

	glutSetWindowTitle(statusLine.c_str());

	glutSwapBuffers();
}

void resize(int w, int h) {
	fractal->resize(w, h);
}

void idle() {
	glutPostRedisplay();
}

void flip(int &y) {
	y=glutGet(GLUT_WINDOW_HEIGHT) - y;
}

void mouseDown(int button, int state, int x, int y) {
	flip(y);
	if(state == GLUT_DOWN){ 
		if(button == 0)
			fractal->mouseDown(Left, x, y);
		if(button == 1)
			fractal->mouseDown(Right, x, y);
	}

	if(state == GLUT_UP) {
		if(button == GLUT_WHEEL_UP)
			fractal->mouseWheelUp();
		if(button == GLUT_WHEEL_DOWN)
			fractal->mouseWheelDown();
	}
}

void mouseMove(int x, int y) {
	flip(y);
	fractal->mouseMove(x, y);
}

void mouseHover(int x, int y) {
	flip(y);
	fractal->mouseHover(x, y);
}

void keyDown(unsigned char key, int x, int y) {
	if(key==0x1b)
		exit(0);

	if(key=='f')
		toggleFullscreen();

	fractal->keyDown(key);
}

void keyUp(unsigned char key, int x, int y) {
	fractal->keyUp(key);
}

void createFractal(const string& type) {
	fractalType=type;

	if(type == "julia4") {
		fractal=new Julia4(0.003f);
	}else if(type=="mand") {
		fractal=new ShaderFractal("mand.sl");
	} else if(type=="julia") {
		fractal=new Julia2();
	} else {
		fractal=new InverseJulia2(complex<float>(-.74543f, .11301f));
	}
}

void registerCallbacks() {
	glutReshapeFunc(resize);

	glutKeyboardFunc(keyDown);
	glutKeyboardUpFunc(keyUp);

	glutDisplayFunc(draw);
	glutIdleFunc(idle);
	glutMouseFunc(mouseDown);
	glutMotionFunc(mouseMove);
	glutPassiveMotionFunc(mouseHover);
}

int main(int argc, char *argv[])
{
	glutInit(&argc, argv);

	string fractalType("inverseJulia2");
	if(argc == 2) fractalType.assign(argv[1]);

	glutCreateWindow(fractalType.c_str());
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

	check(glewInit());
	versionCheck();

	createFractal(fractalType);

	registerCallbacks();
	glutMainLoop();

	return 0;
}